//
// Copyright 2021 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: x4xx_qsfp_wrapper_tb
//
// Description:
//
//   Testbench for x4xx_qsfp_wrapper.
//
// Parameters:
//
//   TEST_NAME : String added to test output
//   PROTOCOL  : Must be {100Gbe, 10GbE, 1GbE, Aurora, Disabled}
//   USE_MAC   : When set simulate through MAC and PHY. When false, cut before
//               the MAC.
//

`include "./x4xx_mgt_types.vh"


module x4xx_qsfp_wrapper_tb #(
  parameter       TEST_NAME  = "x4xx_qsfp_wrapper_tb",
  parameter       PROTOCOL0  = `MGT_10GbE,
  parameter       PROTOCOL1  = `MGT_Disabled,
  parameter       PROTOCOL2  = `MGT_Disabled,
  parameter       PROTOCOL3  = `MGT_Disabled,
  parameter       CHDR_W     = 64,
  parameter logic USE_MAC    = 1
) (
  /* no IO */
);
  // Include macros and time declarations for use with PkgTestExec
  `define TEST_EXEC_OBJ test
  `include "test_exec.svh"
  import PkgAxiStreamBfm::*;
  import PkgAxiLiteBfm::*;
  import PkgTestExec::*;
  import PkgChdrUtils::*;
  import PkgChdrBfm::*;
  import PkgEthernet::*;

  //---------------------------------------------------------------------------
  // Local Parameters
  //---------------------------------------------------------------------------

  localparam int CPU_W  = 64;

  localparam int ENET_W = PROTOCOL0 == `MGT_100GbE?512:64;
  // 10gbe Mac AUTOEXPANDS small packets > 64 bit
  localparam AUTOEXPAND_TO_64 = PROTOCOL0 == `MGT_100GbE?0:USE_MAC;

  localparam [7:0]  PORTNUM      = 0;
  localparam        MDIO_EN      = 0;
  localparam [4:0]  MDIO_PHYADDR = 0;
  localparam [15:0] RFNOC_PROTOVER  = {8'd1, 8'd0};

  localparam ENET_USER_W  = $clog2(ENET_W/8)+1;
  localparam CPU_USER_W   = $clog2(CPU_W/8)+1;
  localparam CHDR_USER_W  = $clog2(CHDR_W/8)+1;
  // allows the DUT to push full words and tb does not check tuser/tkeep of packets it's transmitting
  localparam IGNORE_EXTRA_DATA = 0;

  localparam PREAMBLE_BYTES  = (PROTOCOL0 == `MGT_100GbE) ? 0 : 0;
  localparam USER_CLK_PERIOD = (PROTOCOL0 == `MGT_100GbE) ? 3.1 : 6.4;
  localparam SV_ETH_IFC = 1;

  localparam logic[3:0] DISABLED = {PROTOCOL3 == `MGT_Disabled,
                                    PROTOCOL2 == `MGT_Disabled,
                                    PROTOCOL1 == `MGT_Disabled,
                                    PROTOCOL0 == `MGT_Disabled};
  localparam logic[3:0] IS10GBE  = {PROTOCOL3 == `MGT_10GbE,
                                    PROTOCOL2 == `MGT_10GbE,
                                    PROTOCOL1 == `MGT_10GbE,
                                    PROTOCOL0 == `MGT_10GbE};

  //---------------------------------------------------------------------------
  // Clocks and resets
  //---------------------------------------------------------------------------

  bit clk200,clk100,clk40,clk156p25,userclk,sim_userclk;
  bit clk200_rst,clk100_rst,clk40_rst,clk40_rstn,refclk_rst,userclk_rst,sim_userclk_rst;
  logic  refclk_p,refclk_n;
  logic done = 0;


  // 322.2666 MHz ref - clock generated by 100G core.
  // 156.25   MHz ref - clock generated by 10G core.
  // If we simulate the actual xilinx core, don't use this clock
  sim_clock_gen #(.PERIOD(USER_CLK_PERIOD), .AUTOSTART(1))
    userclk_gen  (.clk(sim_userclk), .rst(sim_userclk_rst));
  //156.25 MHz ref
  sim_clock_gen #(.PERIOD(6.4), .AUTOSTART(1))
    refclk_gen   (.clk(clk156p25), .rst(refclk_rst));
  always_comb begin
    refclk_p = clk156p25; //156.25
    refclk_n = !clk156p25;
  end

  sim_clock_gen #(.PERIOD(5.0), .AUTOSTART(1))
    clk200_gen   (.clk(clk200), .rst(clk200_rst));
  sim_clock_gen #(.PERIOD(10.0), .AUTOSTART(1))
    clk100_gen   (.clk(clk100), .rst(clk100_rst));
  sim_clock_gen #(.PERIOD(25.0), .AUTOSTART(1))
    clk40_gen    (.clk(clk40), .rst(clk40_rst));
  always_comb clk40_rstn = !clk40_rst;

  //---------------------------------------------------------------------------
  // Bus Functional Models
  //---------------------------------------------------------------------------
  TestExec test = new();

  AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W))
    eth_tx [3:0] (userclk, userclk_rst);
  AxiStreamIf #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W))
    eth_rx [3:0] (userclk, userclk_rst);

  AxiStreamIf #(.DATA_WIDTH(CHDR_W),.USER_WIDTH(CHDR_USER_W),.TKEEP(0),.TUSER(0))
    v2e [3:0] (clk200, clk200_rst);
  AxiStreamIf #(.DATA_WIDTH(CHDR_W),.USER_WIDTH(CHDR_USER_W),.TKEEP(0),.TUSER(0))
    e2v [3:0] (clk200, clk200_rst);

  AxiStreamIf #(.DATA_WIDTH(CPU_W),.USER_WIDTH(CPU_USER_W),.TUSER(0))
    c2e [3:0] (clk40, clk40_rst);
  AxiStreamIf #(.DATA_WIDTH(CPU_W),.USER_WIDTH(CPU_USER_W),.TUSER(0))
    e2c [3:0] (clk40, clk40_rst);

  AxiLiteIf #(.DATA_WIDTH(32),.ADDR_WIDTH(40))
    s_axi (clk40, clk40_rst);

  // Bus functional model for a axi_stream controller
  AxiStreamBfm #(.DATA_WIDTH(ENET_W),.USER_WIDTH(ENET_USER_W)) eth [];
  AxiStreamBfm #(.DATA_WIDTH(CHDR_W),.USER_WIDTH(CHDR_USER_W),.TKEEP(0),.TUSER(0)) v [];
  AxiStreamBfm #(.DATA_WIDTH(CPU_W),.USER_WIDTH(CPU_USER_W),.TUSER(0)) cpu [];
  AxiLiteBfm #(.DATA_WIDTH(32),.ADDR_WIDTH(40)) axi = new(.master(s_axi));

  //---------------------------------------------------------------------------
  // Instantiate DUT
  //---------------------------------------------------------------------------

  logic [3:0] tx_p,tx_n,rx_p,rx_n;
  logic [3:0] [31:0] port_info;
  logic [15:0] device_id;
  logic [3:0] link_up, activity;
  logic QSFP_MODPRS_n =1'b0;

  logic [3:0] eth_rx_irq;
  logic [3:0] eth_tx_irq;

  // ETH DMA AXI To CPU
  AxiIf_v #(.DATA_WIDTH(128),.ADDR_WIDTH(49))
    axi_hp_v (clk40, clk40_rst);

  AxiLiteIf_v #(.DATA_WIDTH(32),.ADDR_WIDTH(40))
    s_axi_v (clk40, clk40_rst);
  `include "../../../../lib/axi4lite_sv/axi_lite.vh"
  `include "../../../../lib/axi4_sv/axi.vh"
  always_comb begin
    `AXI4LITE_ASSIGN(s_axi_v,s_axi)
    axi_hp_v.arready = 1'b1;
    axi_hp_v.awready = 1'b1;
    axi_hp_v.wready  = 1'b1;
    axi_hp_v.rdata      = '0;
    axi_hp_v.rresp[1:0] = 2'b0;
    axi_hp_v.rlast      = 1'b0;
    axi_hp_v.rvalid     = 1'b0;
    axi_hp_v.bresp[1:0] = 2'b0;
    axi_hp_v.bvalid     = 1'b0;
  end

  `define MGT_IO0 dut.x4xx_qsfp_wrapper_i.mgt_lanes.lane_loop[0].x4xx_mgt_io_core_i
  `define MGT_IO1 dut.x4xx_qsfp_wrapper_i.mgt_lanes.lane_loop[1].x4xx_mgt_io_core_i
  `define MGT_IO2 dut.x4xx_qsfp_wrapper_i.mgt_lanes.lane_loop[2].x4xx_mgt_io_core_i
  `define MGT_IO3 dut.x4xx_qsfp_wrapper_i.mgt_lanes.lane_loop[3].x4xx_mgt_io_core_i
  `define QSFP_W dut.x4xx_qsfp_wrapper_i

  x4xx_qsfp_wrapper_temp #(
    .PROTOCOL0     (PROTOCOL0),
    .PROTOCOL1     (PROTOCOL1),
    .PROTOCOL2     (PROTOCOL2),
    .PROTOCOL3     (PROTOCOL3),
    .CHDR_W        (CHDR_W),
    .PORTNUM       (0)
  ) dut (
    .areset          (refclk_rst),
    .refclk_p        (refclk_p),
    .refclk_n        (refclk_n),
    .bus_rst         (clk200_rst),
    .clk40_rst       (clk40_rst),
    .clk100          (clk100),
    .bus_clk         (clk200),
    .clk40           (clk40),
    `AXI4_PORT_ASSIGN_NR(axi_hp,axi_hp_v)
    `AXI4LITE_PORT_ASSIGN_NR(s_axi,s_axi_v)
    .tx_p            (tx_p),
    .tx_n            (tx_n),
    .rx_p            (rx_p),
    .rx_n            (rx_n),

    .e2v_tdata       ({e2v[3].tdata,  e2v[2].tdata,  e2v[1].tdata,  e2v[0].tdata}),
    .e2v_tlast       ({e2v[3].tlast,  e2v[2].tlast,  e2v[1].tlast,  e2v[0].tlast}),
    .e2v_tvalid      ({e2v[3].tvalid, e2v[2].tvalid, e2v[1].tvalid, e2v[0].tvalid}),
    .e2v_tready      ({e2v[3].tready, e2v[2].tready, e2v[1].tready, e2v[0].tready}),
    .v2e_tdata       ({v2e[3].tdata,  v2e[2].tdata,  v2e[1].tdata,  v2e[0].tdata}),
    .v2e_tlast       ({v2e[3].tlast,  v2e[2].tlast,  v2e[1].tlast,  v2e[0].tlast}),
    .v2e_tvalid      ({v2e[3].tvalid, v2e[2].tvalid, v2e[1].tvalid, v2e[0].tvalid}),
    .v2e_tready      ({v2e[3].tready, v2e[2].tready, v2e[1].tready, v2e[0].tready}),

    .eth_tx_irq      (eth_tx_irq),
    .eth_rx_irq      (eth_rx_irq),

    .rx_rec_clk_out  (),
    .device_id       (device_id),

    .port_info_0     (port_info[0]),
    .port_info_1     (port_info[1]),
    .port_info_2     (port_info[2]),
    .port_info_3     (port_info[3]),

    .link_up         (link_up),
    .activity        (activity)
  );


  //---------------------------------------------------------------------------
  // Connect to e2c c2e
  //---------------------------------------------------------------------------
  // Ideally simulation would update to test Xilinx DMA block, but as a quick
  // fix we are simulating just to the AXI stream bus.
  if (PROTOCOL0 == `MGT_100GbE || PROTOCOL0 == `MGT_10GbE) begin
    always_comb begin
      e2c[0].tdata  = `QSFP_W.mgt_lanes.lane_loop[0].eth_port.e2c.tdata;
      e2c[0].tuser  = `QSFP_W.mgt_lanes.lane_loop[0].eth_port.e2c.tuser;
      e2c[0].tkeep  = `QSFP_W.mgt_lanes.lane_loop[0].eth_port.e2c.tkeep;
      e2c[0].tlast  = `QSFP_W.mgt_lanes.lane_loop[0].eth_port.e2c.tlast;
      e2c[0].tvalid = `QSFP_W.mgt_lanes.lane_loop[0].eth_port.e2c.tvalid;
      force `QSFP_W.mgt_lanes.lane_loop[0].eth_port.e2c.tready = e2c[0].tready;

      force `QSFP_W.mgt_lanes.lane_loop[0].eth_port.c2e.tdata  = c2e[0].tdata;
      force `QSFP_W.mgt_lanes.lane_loop[0].eth_port.c2e.tuser  = c2e[0].tuser;
      force `QSFP_W.mgt_lanes.lane_loop[0].eth_port.c2e.tkeep  = c2e[0].tkeep;
      force `QSFP_W.mgt_lanes.lane_loop[0].eth_port.c2e.tlast  = c2e[0].tlast;
      force `QSFP_W.mgt_lanes.lane_loop[0].eth_port.c2e.tvalid = c2e[0].tvalid;
      c2e[0].tready = `QSFP_W.mgt_lanes.lane_loop[0].eth_port.c2e.tready;
    end
  end
  if (PROTOCOL1 == `MGT_10GbE) begin
    always_comb begin
      e2c[1].tdata  = `QSFP_W.mgt_lanes.lane_loop[1].eth_port.e2c.tdata;
      e2c[1].tuser  = `QSFP_W.mgt_lanes.lane_loop[1].eth_port.e2c.tuser;
      e2c[1].tkeep  = `QSFP_W.mgt_lanes.lane_loop[1].eth_port.e2c.tkeep;
      e2c[1].tlast  = `QSFP_W.mgt_lanes.lane_loop[1].eth_port.e2c.tlast;
      e2c[1].tvalid = `QSFP_W.mgt_lanes.lane_loop[1].eth_port.e2c.tvalid;
      force `QSFP_W.mgt_lanes.lane_loop[1].eth_port.e2c.tready = e2c[1].tready;

      force `QSFP_W.mgt_lanes.lane_loop[1].eth_port.c2e.tdata  = c2e[1].tdata;
      force `QSFP_W.mgt_lanes.lane_loop[1].eth_port.c2e.tuser  = c2e[1].tuser;
      force `QSFP_W.mgt_lanes.lane_loop[1].eth_port.c2e.tkeep  = c2e[1].tkeep;
      force `QSFP_W.mgt_lanes.lane_loop[1].eth_port.c2e.tlast  = c2e[1].tlast;
      force `QSFP_W.mgt_lanes.lane_loop[1].eth_port.c2e.tvalid = c2e[1].tvalid;
      c2e[1].tready = `QSFP_W.mgt_lanes.lane_loop[1].eth_port.c2e.tready;
    end
  end
  if (PROTOCOL2 == `MGT_10GbE) begin
    always_comb begin
      e2c[2].tdata  = `QSFP_W.mgt_lanes.lane_loop[2].eth_port.e2c.tdata;
      e2c[2].tuser  = `QSFP_W.mgt_lanes.lane_loop[2].eth_port.e2c.tuser;
      e2c[2].tkeep  = `QSFP_W.mgt_lanes.lane_loop[2].eth_port.e2c.tkeep;
      e2c[2].tlast  = `QSFP_W.mgt_lanes.lane_loop[2].eth_port.e2c.tlast;
      e2c[2].tvalid = `QSFP_W.mgt_lanes.lane_loop[2].eth_port.e2c.tvalid;
      force `QSFP_W.mgt_lanes.lane_loop[2].eth_port.e2c.tready = e2c[2].tready;

      force `QSFP_W.mgt_lanes.lane_loop[2].eth_port.c2e.tdata  = c2e[2].tdata;
      force `QSFP_W.mgt_lanes.lane_loop[2].eth_port.c2e.tuser  = c2e[2].tuser;
      force `QSFP_W.mgt_lanes.lane_loop[2].eth_port.c2e.tkeep  = c2e[2].tkeep;
      force `QSFP_W.mgt_lanes.lane_loop[2].eth_port.c2e.tlast  = c2e[2].tlast;
      force `QSFP_W.mgt_lanes.lane_loop[2].eth_port.c2e.tvalid = c2e[2].tvalid;
      c2e[2].tready = `QSFP_W.mgt_lanes.lane_loop[2].eth_port.c2e.tready;
    end
  end
  if (PROTOCOL3 == `MGT_10GbE) begin
    always_comb begin
      e2c[3].tdata  = `QSFP_W.mgt_lanes.lane_loop[3].eth_port.e2c.tdata;
      e2c[3].tuser  = `QSFP_W.mgt_lanes.lane_loop[3].eth_port.e2c.tuser;
      e2c[3].tkeep  = `QSFP_W.mgt_lanes.lane_loop[3].eth_port.e2c.tkeep;
      e2c[3].tlast  = `QSFP_W.mgt_lanes.lane_loop[3].eth_port.e2c.tlast;
      e2c[3].tvalid = `QSFP_W.mgt_lanes.lane_loop[3].eth_port.e2c.tvalid;
      force `QSFP_W.mgt_lanes.lane_loop[3].eth_port.e2c.tready = e2c[3].tready;

      force `QSFP_W.mgt_lanes.lane_loop[3].eth_port.c2e.tdata  = c2e[3].tdata;
      force `QSFP_W.mgt_lanes.lane_loop[3].eth_port.c2e.tuser  = c2e[3].tuser;
      force `QSFP_W.mgt_lanes.lane_loop[3].eth_port.c2e.tkeep  = c2e[3].tkeep;
      force `QSFP_W.mgt_lanes.lane_loop[3].eth_port.c2e.tlast  = c2e[3].tlast;
      force `QSFP_W.mgt_lanes.lane_loop[3].eth_port.c2e.tvalid = c2e[3].tvalid;
      c2e[3].tready = `QSFP_W.mgt_lanes.lane_loop[3].eth_port.c2e.tready;
    end
  end

  //---------------------------------------------------------------------------
  // Connect to Internal Bus
  //---------------------------------------------------------------------------

  logic [3:0] model_link_up;
  if (USE_MAC) begin : use_mac
    if (PROTOCOL0 == `MGT_100GbE) begin : use_mac_100
      model_100gbe model_100gbe_i (
        .areset        (refclk_rst),
        .ref_clk       (refclk_p),
        .tx_p          (rx_p),
        .tx_n          (rx_n),
        .rx_p          (tx_p),
        .rx_n          (tx_n),
        .mgt_clk       (userclk),
        .mgt_rst       (userclk_rst),
        .link_up       (model_link_up[0]),
        .mgt_tx        (eth_rx[0]),
        .mgt_rx        (eth_tx[0])
      );
    end else begin : use_mac_single_lane
      if (IS10GBE[0]) begin : use_mac0_10
        logic mgt_clk, mgt_rst;
        model_10gbe #(.PORTNUM(0)) model_10gbe_0(
          .areset        (refclk_rst),
          .ref_clk       (refclk_p),
          .tx_p          (rx_p[0]),
          .tx_n          (rx_n[0]),
          .rx_p          (tx_p[0]),
          .rx_n          (tx_n[0]),
          .mgt_clk       (userclk),
          .mgt_rst       (userclk_rst),
          .link_up       (model_link_up[0]),
          .mgt_tx        (eth_rx[0]),
          .mgt_rx        (eth_tx[0])
        );
      end
      if (IS10GBE[1]) begin : use_mac1_10
        logic mgt_clk, mgt_rst;
        model_10gbe #(.PORTNUM(1)) model_10gbe_1(
          .areset        (refclk_rst),
          .ref_clk       (refclk_p),
          .tx_p          (rx_p[1]),
          .tx_n          (rx_n[1]),
          .rx_p          (tx_p[1]),
          .rx_n          (tx_n[1]),
          .mgt_clk       (userclk),
          .mgt_rst       (userclk_rst),
          .link_up       (model_link_up[1]),
          .mgt_tx        (eth_rx[1]),
          .mgt_rx        (eth_tx[1])
        );
      end
      if (IS10GBE[2]) begin : use_mac2_10
        logic mgt_clk, mgt_rst;
        model_10gbe #(.PORTNUM(2)) model_10gbe_2(
          .areset        (refclk_rst),
          .ref_clk       (refclk_p),
          .tx_p          (rx_p[2]),
          .tx_n          (rx_n[2]),
          .rx_p          (tx_p[2]),
          .rx_n          (tx_n[2]),
          .mgt_clk       (userclk),
          .mgt_rst       (userclk_rst),
          .link_up       (model_link_up[2]),
          .mgt_tx        (eth_rx[2]),
          .mgt_rx        (eth_tx[2])
        );
      end
      if (IS10GBE[3]) begin : use_mac3_10
        logic mgt_clk, mgt_rst;
        model_10gbe #(.PORTNUM(3)) model_10gbe_3(
          .areset        (refclk_rst),
          .ref_clk       (refclk_p),
          .tx_p          (rx_p[3]),
          .tx_n          (rx_n[3]),
          .rx_p          (tx_p[3]),
          .rx_n          (tx_n[3]),
          .mgt_clk       (userclk),
          .mgt_rst       (userclk_rst),
          .link_up       (model_link_up[3]),
          .mgt_tx        (eth_rx[3]),
          .mgt_rx        (eth_tx[3])
        );
      end
    end : use_mac_single_lane
  end else begin : skip_mac
    assign model_link_up = 1;
    always_comb begin
      userclk     = sim_userclk;
      userclk_rst = sim_userclk_rst;
    end
    if (PROTOCOL0 == `MGT_100GbE) begin : skip_mac_100
      always_comb begin
        force `MGT_IO0.core_100g.eth_100g_i.eth_100g_bd_i.gt_txusrclk2 = userclk;
        force `MGT_IO0.core_100g.eth_100g_i.link_up = !userclk_rst;

        eth_tx[0].tdata  = `MGT_IO0.core_100g.eth_100g_i.eth100g_tx.tdata;
        eth_tx[0].tkeep  = `MGT_IO0.core_100g.eth_100g_i.eth100g_tx.tkeep;
        eth_tx[0].tuser  = eth_tx[0].keep2trailing(eth_tx[0].tkeep);
        eth_tx[0].tlast  = `MGT_IO0.core_100g.eth_100g_i.eth100g_tx.tlast;
        eth_tx[0].tvalid = `MGT_IO0.core_100g.eth_100g_i.eth100g_tx.tvalid;
        force `MGT_IO0.core_100g.eth_100g_i.eth100g_tx.tready = eth_tx[0].tready;
        force `MGT_IO0.core_100g.eth_100g_i.lbus_tx[0] = 0;
        force `MGT_IO0.core_100g.eth_100g_i.lbus_tx[1] = 0;
        force `MGT_IO0.core_100g.eth_100g_i.lbus_tx[2] = 0;
        force `MGT_IO0.core_100g.eth_100g_i.lbus_tx[3] = 0;

        force `MGT_IO0.core_100g.eth_100g_i.eth100g_rx.tdata  = eth_rx[0].tdata;
        force `MGT_IO0.core_100g.eth_100g_i.eth100g_rx.tuser  = eth_rx[0].tuser;
        force `MGT_IO0.core_100g.eth_100g_i.eth100g_rx.tkeep  = eth_rx[0].tkeep;
        force `MGT_IO0.core_100g.eth_100g_i.eth100g_rx.tlast  = eth_rx[0].tlast;
        force `MGT_IO0.core_100g.eth_100g_i.eth100g_rx.tvalid = eth_rx[0].tvalid;
        eth_rx[0].tready = 1;
      end
    end else begin : skip_mac_single_lane
      if (IS10GBE[0]) begin : skip_mac0_10
        always_comb begin
          force `MGT_IO0.mgt_clk = userclk;
          force `MGT_IO0.mgt_rst = userclk_rst;
          eth_tx[0].tdata  = `MGT_IO0.core_10g.eth_10g_i.mgt_tx.tdata;
          eth_tx[0].tuser  = `MGT_IO0.core_10g.eth_10g_i.mgt_tx.tuser;
          eth_tx[0].tkeep  = `MGT_IO0.core_10g.eth_10g_i.mgt_tx.tkeep;
          eth_tx[0].tlast  = `MGT_IO0.core_10g.eth_10g_i.mgt_tx.tlast;
          eth_tx[0].tvalid = `MGT_IO0.core_10g.eth_10g_i.mgt_tx.tvalid;
          force `MGT_IO0.core_10g.eth_10g_i.mgt_tx.tready = eth_tx[0].tready;

          force `MGT_IO0.core_10g.eth_10g_i.mgt_rx.tdata = eth_rx[0].tdata;
          force `MGT_IO0.core_10g.eth_10g_i.mgt_rx.tuser = eth_rx[0].tuser;
          force `MGT_IO0.core_10g.eth_10g_i.mgt_rx.tlast = eth_rx[0].tlast;
          force `MGT_IO0.core_10g.eth_10g_i.mgt_rx.tvalid = eth_rx[0].tvalid;
          eth_rx[0].tready = 1;
        end
      end : skip_mac0_10
      if (IS10GBE[1]) begin : skip_mac1_10
        always_comb begin
          force `MGT_IO1.mgt_clk = userclk;
          force `MGT_IO1.mgt_rst = userclk_rst;
          eth_tx[1].tdata  = `MGT_IO1.core_10g.eth_10g_i.mgt_tx.tdata;
          eth_tx[1].tuser  = `MGT_IO1.core_10g.eth_10g_i.mgt_tx.tuser;
          eth_tx[1].tkeep  = `MGT_IO1.core_10g.eth_10g_i.mgt_tx.tkeep;
          eth_tx[1].tlast  = `MGT_IO1.core_10g.eth_10g_i.mgt_tx.tlast;
          eth_tx[1].tvalid = `MGT_IO1.core_10g.eth_10g_i.mgt_tx.tvalid;
          force `MGT_IO1.core_10g.eth_10g_i.mgt_tx.tready = eth_tx[1].tready;

          force `MGT_IO1.core_10g.eth_10g_i.mgt_rx.tdata = eth_rx[1].tdata;
          force `MGT_IO1.core_10g.eth_10g_i.mgt_rx.tuser = eth_rx[1].tuser;
          force `MGT_IO1.core_10g.eth_10g_i.mgt_rx.tlast = eth_rx[1].tlast;
          force `MGT_IO1.core_10g.eth_10g_i.mgt_rx.tvalid = eth_rx[1].tvalid;
          eth_rx[1].tready = 1;
        end
      end : skip_mac1_10
      if (IS10GBE[2]) begin : skip_mac2_10
        always_comb begin
          force `MGT_IO2.mgt_clk = userclk;
          force `MGT_IO2.mgt_rst = userclk_rst;
          eth_tx[2].tdata  = `MGT_IO2.core_10g.eth_10g_i.mgt_tx.tdata;
          eth_tx[2].tuser  = `MGT_IO2.core_10g.eth_10g_i.mgt_tx.tuser;
          eth_tx[2].tkeep  = `MGT_IO2.core_10g.eth_10g_i.mgt_tx.tkeep;
          eth_tx[2].tlast  = `MGT_IO2.core_10g.eth_10g_i.mgt_tx.tlast;
          eth_tx[2].tvalid = `MGT_IO2.core_10g.eth_10g_i.mgt_tx.tvalid;
          force `MGT_IO2.core_10g.eth_10g_i.mgt_tx.tready = eth_tx[2].tready;

          force `MGT_IO2.core_10g.eth_10g_i.mgt_rx.tdata = eth_rx[2].tdata;
          force `MGT_IO2.core_10g.eth_10g_i.mgt_rx.tuser = eth_rx[2].tuser;
          force `MGT_IO2.core_10g.eth_10g_i.mgt_rx.tlast = eth_rx[2].tlast;
          force `MGT_IO2.core_10g.eth_10g_i.mgt_rx.tvalid = eth_rx[2].tvalid;
          eth_rx[2].tready = 1;
        end
      end : skip_mac2_10
      if (IS10GBE[3]) begin : skip_mac3_10
        always_comb begin
          force `MGT_IO3.mgt_clk = userclk;
          force `MGT_IO3.mgt_rst = userclk_rst;
          eth_tx[3].tdata  = `MGT_IO3.core_10g.eth_10g_i.mgt_tx.tdata;
          eth_tx[3].tuser  = `MGT_IO3.core_10g.eth_10g_i.mgt_tx.tuser;
          eth_tx[3].tkeep  = `MGT_IO3.core_10g.eth_10g_i.mgt_tx.tkeep;
          eth_tx[3].tlast  = `MGT_IO3.core_10g.eth_10g_i.mgt_tx.tlast;
          eth_tx[3].tvalid = `MGT_IO3.core_10g.eth_10g_i.mgt_tx.tvalid;
          force `MGT_IO3.core_10g.eth_10g_i.mgt_tx.tready = eth_tx[3].tready;

          force `MGT_IO3.core_10g.eth_10g_i.mgt_rx.tdata = eth_rx[3].tdata;
          force `MGT_IO3.core_10g.eth_10g_i.mgt_rx.tuser = eth_rx[3].tuser;
          force `MGT_IO3.core_10g.eth_10g_i.mgt_rx.tlast = eth_rx[3].tlast;
          force `MGT_IO3.core_10g.eth_10g_i.mgt_rx.tvalid = eth_rx[3].tvalid;
          eth_rx[3].tready = 1;
        end
      end : skip_mac3_10
    end : skip_mac_single_lane
  end

  //---------------------------------------------------------------------------
  // Reset
  //---------------------------------------------------------------------------

  task test_reset();
    wait(!clk200_rst && !clk100_rst && !clk40_rst);
    repeat (10) @(posedge clk40);
  endtask : test_reset

  //---------------------------------------------------------------------------
  // Test Registers
  //---------------------------------------------------------------------------

  // register offset for DMA controller
  localparam REG_DMA                  = 'h0;
  localparam MM2S_DMACR               = REG_DMA + 'h0;

  // register offsets from x4xx_mgt_io_core
  // MGT_IO Registers (NI_XGE registers)
  localparam REG_BASE_SFP_IO          = 32'h8000;
  localparam REG_PORT_INFO            = REG_BASE_SFP_IO + 'h0;
  localparam REG_MAC_CTRL_STATUS      = REG_BASE_SFP_IO + 'h4;
  localparam REG_PHY_CTRL_STATUS      = REG_BASE_SFP_IO + 'h8;
  localparam REG_MAC_LED_CTL          = REG_BASE_SFP_IO + 'hC;

  // Ethernet specific
  localparam REG_ETH_MDIO_BASE        = REG_BASE_SFP_IO + 'h10;

  // Aurora specific
  localparam REG_AURORA_OVERRUNS      = REG_BASE_SFP_IO + 'h20;
  localparam REG_CHECKSUM_ERRORS      = REG_BASE_SFP_IO + 'h24;
  localparam REG_BIST_CHECKER_SAMPS   = REG_BASE_SFP_IO + 'h28;
  localparam REG_BIST_CHECKER_ERRORS  = REG_BASE_SFP_IO + 'h2C;

  // At 0x9000 The OpenCores XGE MAC registers exist.

  // Set BASE for UIO - The package file defines the registers at +0x1000.
  // NOTE that 0x9000/0x9004 has a local copy of the MAC REGISTER.
  localparam BASE                 = 32'h9000;
  localparam REG_AWIDTH = 16;
  `include "../../../../lib/rfnoc/xport_sv/eth_regs.vh"

  // 100gbe Registers
  localparam CMAC_BASE = 32'hC000;
  localparam REG_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG1   = CMAC_BASE+ 32'h0048;
  localparam REG_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG1  = CMAC_BASE+ 32'h0034;

  task test_registers(int lane);
    localparam [7:0] COMPAT_NUM   = 8'd2;
    logic      [7:0] MGT_PROTOCOL;

    automatic resp_t resp;
    automatic logic  activity = 0;
    automatic logic  reg_link_up = 0;
    automatic logic  [31:0] port_info;
    automatic int    data = 0;
    automatic int    LANE_BASE = 32'h10000 * lane;
    string lane_str;
    lane_str.itoa(lane);

    test.start_test({TEST_NAME," ",lane_str," Test/Setup Registers"}, 30us);

    case (lane)
      0 : MGT_PROTOCOL = PROTOCOL0;
      1 : MGT_PROTOCOL = PROTOCOL1;
      2 : MGT_PROTOCOL = PROTOCOL2;
      3 : MGT_PROTOCOL = PROTOCOL3;
    endcase

    // testing AxiLite Model
    repeat (4) begin
      axi.wr(LANE_BASE+REG_MAC_LSB,++data);
      axi.rd(LANE_BASE+REG_MAC_LSB,data);
      axi.wr(LANE_BASE+REG_MAC_MSB,++data);
      axi.rd(LANE_BASE+REG_MAC_MSB,data);
    end

    // try with different idle states
    axi.ready_idle_state = 1;
    repeat (16) begin
      axi.wr(LANE_BASE+REG_MAC_LSB,++data);
    end
    repeat (4) axi.rd(LANE_BASE+REG_MAC_LSB,data);
    axi.ready_idle_state = 0;
    repeat (16) begin
      axi.wr(LANE_BASE+REG_MAC_LSB,++data);
    end
    repeat (4) axi.rd(LANE_BASE+REG_MAC_LSB,data);

    if ((MGT_PROTOCOL == `MGT_100GbE && !USE_MAC) || (MGT_PROTOCOL == `MGT_10GbE && USE_MAC)) begin
      reg_link_up = 1;
    end

    port_info = {COMPAT_NUM, 6'h0, activity, reg_link_up, MGT_PROTOCOL, PORTNUM};
    axi.rd(LANE_BASE+REG_PORT_INFO,port_info);
    // status/ctrl isn't used yet
    if      (MGT_PROTOCOL == `MGT_100GbE) axi.rd(LANE_BASE+REG_MAC_CTRL_STATUS,0);
    else if (MGT_PROTOCOL == `MGT_10GbE)
      if (USE_MAC) axi.rd(LANE_BASE+REG_MAC_CTRL_STATUS,32'h0000_0000);
      else         axi.rd(LANE_BASE+REG_MAC_CTRL_STATUS,32'h0000_0080);
    else assert(1);


    axi.rd(LANE_BASE+REG_PHY_CTRL_STATUS,(MGT_PROTOCOL == `MGT_10GbE && USE_MAC) ? reg_link_up : 0);
    // en 0 / value 1
    axi.rd(LANE_BASE+REG_MAC_LED_CTL,0);
    // enabled with value 0
    axi.wr(LANE_BASE+REG_MAC_LED_CTL,1);
    axi.rd(LANE_BASE+REG_MAC_LED_CTL,1);
    activity = 0;
    port_info = {COMPAT_NUM, 6'h0, activity, reg_link_up, MGT_PROTOCOL, PORTNUM};
    axi.rd(LANE_BASE+REG_PORT_INFO,port_info);

    // enabled with value 1
    axi.wr(LANE_BASE+REG_MAC_LED_CTL,3);
    axi.rd(LANE_BASE+REG_MAC_LED_CTL,3);
    activity = 1;
    port_info = {COMPAT_NUM, 6'h0, activity, reg_link_up, MGT_PROTOCOL, PORTNUM};
    axi.rd(LANE_BASE+REG_PORT_INFO,port_info);

    // enet controls led
    axi.wr(LANE_BASE+REG_MAC_LED_CTL,0);
    // unused registers with 100g
    axi.rd(LANE_BASE+REG_AURORA_OVERRUNS,32'h0);
    axi.rd(LANE_BASE+REG_CHECKSUM_ERRORS,32'h0);
    axi.rd(LANE_BASE+REG_BIST_CHECKER_SAMPS,32'h0);
    axi.rd(LANE_BASE+REG_BIST_CHECKER_ERRORS,32'h0);

    // DEF_DEST_MAC/IP/UDP are defined in the
    // sim_ethernet_lib.svh, as the destination
    // addresses. Using the defaults means
    // if I don't change the dest address on
    // a packet it will go to the CHDR
    axi.wr(LANE_BASE+REG_MAC_LSB,DEF_DEST_MAC_ADDR[31:0]);
    axi.wr(LANE_BASE+REG_MAC_MSB,DEF_DEST_MAC_ADDR[47:32]);
    axi.wr(LANE_BASE+REG_IP,DEF_DEST_IP_ADDR);
    axi.wr(LANE_BASE+REG_UDP,DEF_DEST_UDP_PORT);
    axi.wr(LANE_BASE+REG_BRIDGE_ENABLE,1);
    axi.wr(LANE_BASE+REG_BRIDGE_MAC_LSB,DEF_BRIDGE_MAC_ADDR[31:0]);
    axi.wr(LANE_BASE+REG_BRIDGE_MAC_MSB,DEF_BRIDGE_MAC_ADDR[47:32]);
    axi.wr(LANE_BASE+REG_BRIDGE_IP,DEF_BRIDGE_IP_ADDR);
    axi.wr(LANE_BASE+REG_BRIDGE_UDP,DEF_BRIDGE_UDP_PORT);
    axi.wr(LANE_BASE+REG_BRIDGE_ENABLE,0);

    // Readback the values
    axi.rd(LANE_BASE+REG_MAC_LSB,DEF_DEST_MAC_ADDR[31:0]);
    axi.rd(LANE_BASE+REG_MAC_MSB,DEF_DEST_MAC_ADDR[47:32]);
    axi.rd(LANE_BASE+REG_IP,DEF_DEST_IP_ADDR);
    axi.rd(LANE_BASE+REG_UDP,DEF_DEST_UDP_PORT);
    axi.rd(LANE_BASE+REG_BRIDGE_ENABLE,0);
    axi.rd(LANE_BASE+REG_BRIDGE_MAC_LSB,DEF_BRIDGE_MAC_ADDR[31:0]);
    axi.rd(LANE_BASE+REG_BRIDGE_MAC_MSB,DEF_BRIDGE_MAC_ADDR[47:32]);
    axi.rd(LANE_BASE+REG_BRIDGE_IP,DEF_BRIDGE_IP_ADDR);
    axi.rd(LANE_BASE+REG_BRIDGE_UDP,DEF_BRIDGE_UDP_PORT);

    // check the DMA controller
    axi.rd(LANE_BASE+MM2S_DMACR, 32'h00010002);
    // Hit reset bit, and poll for completion
    axi.wr(LANE_BASE+MM2S_DMACR, 32'h4);
    // Make sure reset asserts
    axi.rd(LANE_BASE+MM2S_DMACR, 32'h00010006);
    for (int i = 7; i >= 0; i--) begin
      logic [31:0] readback;
      logic [1:0] resp;
      axi.rd_block(LANE_BASE+MM2S_DMACR, readback, resp);
      if((readback & 32'h4) == 0) begin
        // Reset bit cleared
        break;
      end
      // Give up if it hasn't cleared after several tries
      `ASSERT_ERROR(i != 0, "DMA controller reset failed to deassert.");
    end

    axi.block();
    test.end_test();
  endtask : test_registers

  //---------------------------------------------------------------------------
  // Ethernet to CPU test
  //---------------------------------------------------------------------------
    typedef ChdrData #(CHDR_W)::chdr_word_t chdr_word_t;
    typedef chdr_word_t word_queue_t[$];

    typedef XportStreamPacket #(ENET_W)             EthXportPacket_t;
    typedef AxiStreamPacket   #(ENET_W,ENET_USER_W) EthAxisPacket_t;

    typedef XportStreamPacket #(CPU_W)            CpuXportPacket_t;
    typedef AxiStreamPacket   #(CPU_W,CPU_USER_W) CpuAxisPacket_t;

    typedef XportStreamPacket #(CHDR_W) ChdrXportPacket_t;
    typedef AxiStreamPacket   #(CHDR_W,CHDR_USER_W) ChdrAxisPacket_t;
    typedef ChdrPacket        #(CHDR_W,CHDR_USER_W) ChdrPacket_t;

  task automatic test_ethcpu(int num_samples[$], int ERROR_PROB=2, int EXPECT_DROPS=0);
    fork
      if (!DISABLED[0]) test_ethcpu_lane(0,num_samples,ERROR_PROB,EXPECT_DROPS);
      if (!DISABLED[1]) test_ethcpu_lane(1,num_samples,ERROR_PROB,EXPECT_DROPS);
      if (!DISABLED[2]) test_ethcpu_lane(2,num_samples,ERROR_PROB,EXPECT_DROPS);
      if (!DISABLED[3]) test_ethcpu_lane(3,num_samples,ERROR_PROB,EXPECT_DROPS);
    join
  endtask : test_ethcpu

  task automatic test_ethcpu_lane(int lane, int num_samples[$], int ERROR_PROB=2, int EXPECT_DROPS=0);
    TestExec test_e2c = new();
    automatic EthXportPacket_t send[$];
    automatic CpuXportPacket_t expected[$];
    automatic int sample_sum = 0;
    string lane_str;
    lane_str.itoa(lane);

    test_e2c.start_test({TEST_NAME," ",lane_str," Ethernet to CPU"}, 60us*5);
    // This path is
    //   eth_rx -> s_mac(eth_adapter) -> s_mac(eth_dispatch) ->
    ////   in_reg(AXI_FIFO)(SIZE=1)
    //   (eth_dispatch) in -> STATMACHINE (Dispatch) + cpu ->
    ////   out_reg_cpu(AXI_FIFO)(SIZE=1)
    //   (eth_dispatch) o_cpu ->
    ////   cpu_out_gate(AXI_GATE)(SIZE=11)
    //   (eth_dispatch) m_cpu -> (eth_adapter) e2c_chdr -> e2c_fifo
    ////   cpu_fifo(AXI_FIFO)(SIZE=CPU_FIFO_SIZE)
    //   (eth_adapater) m_cpu -> e2c

    foreach (num_samples[i]) begin
      automatic eth_hdr_t    eth_hdr;
      automatic ipv4_hdr_t   ipv4_hdr;
      automatic udp_hdr_t    udp_hdr;
      automatic raw_pkt_t    pay,udp_raw;
      automatic int          PREAMBLE;

      PREAMBLE = NO_PREAMBLE;

      expected[i] = new;
      send[i] = new;

      udp_hdr.dest_port = 0; //change dest port from default so it goes to cpu
      get_ramp_raw_pkt(.num_samps(num_samples[i]),.ramp_start((sample_sum)%256),
                       .ramp_inc(1),.pkt(pay),.SWIDTH(8));
      sample_sum += num_samples[i];
      udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,pay,
                              .preamble(PREAMBLE));
      send[i].push_bytes(udp_raw);
      send[i].tkeep_to_tuser(.ERROR_PROB(ERROR_PROB));

      // rebuild the expected packet for comparison without the preamble
      udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,pay,
                              .preamble(NO_PREAMBLE));
      expected[i].push_bytes(udp_raw);
      expected[i].tkeep_to_tuser();

    end

    // iterate in descending order so deleting doesn't shift down
    // the packets in future loop iterations.
    for (int i=  num_samples.size()-1 ; i >= 0; i--) begin
      // original only checks for errors in last word.
      if (!SV_ETH_IFC && send[i].has_error()) send[i].set_error();

      // If a packet has an error it shouldn't make it through
      if (send[i].has_error()) expected.delete(i); // MAC ERROR

    end

    fork
      begin // tx_thread
        foreach(send[i])begin
          #1 eth[lane].put(send[i]); // delay causes CPU/CHDR traffic on input to be interleaved.
        end
      end
      begin //rx_thread
        if (EXPECT_DROPS > 0) begin
          automatic int pkt_num = 0;
          automatic logic [31:0] data;
          automatic resp_t resp;
          automatic int drop_count = 0;
          automatic int rcvd_count = 0;
          automatic int drop_count_reg = 0;
          while (expected.size() > 0) begin
            automatic CpuAxisPacket_t  actual_a;
            automatic CpuXportPacket_t actual = new();
            while (!cpu[lane].try_get(actual_a)) begin
               axi.rd_block(REG_CPU_DROPPED,data,resp);
               assert (resp==OKAY);
               drop_count_reg += data;
               // to account for case where we drop the last packet,
               // stop looking if we have received or dropped all the packets
               if (drop_count_reg+rcvd_count >= num_samples.size() && cpu[lane].slave_idle) begin
                 $display("Last packet dropped skipping to the end");
                 expected.delete();
                 drop_count = drop_count_reg;
                 // last packet may still be pending
                 #1us; // wait
                 void'(cpu[lane].try_get(actual_a));
                 break;
               end
            end
            if (expected.size() > 0) begin
              actual.import_axis(actual_a);
              actual.tkeep_to_tuser();

              while (expected.size > 0 && actual.compare_no_user(expected[0],.PRINT_LVL(0))) begin
                void'(expected.pop_front());
                ++drop_count;
                ++pkt_num;
                 $display("Dropped packet %d",pkt_num);
                `ASSERT_ERROR(drop_count < EXPECT_DROPS,"Exceeded anticipated number of dropped packets e2c");
              end
            end
            // expected size could of changed in while loop above
            if (expected.size() > 0) begin
              ++pkt_num;
              ++rcvd_count;
              $display("Rcvd packet   %d",pkt_num);
              void'(expected.pop_front());
            end
          end
          axi.rd_block(REG_CPU_DROPPED,data,resp);
          assert (resp==OKAY);
          drop_count_reg += data;
          if (SV_ETH_IFC) begin
            $display("Verify drop count is %d",drop_count_reg);
            assert(drop_count_reg == drop_count) else $error("Drop count mismatch");
          end
        end else begin
          foreach(expected[i]) begin
            automatic CpuAxisPacket_t  actual_a;
            automatic CpuXportPacket_t actual = new();
            cpu[lane].get(actual_a);
            actual.import_axis(actual_a);
            actual.tkeep_to_tuser();

            `ASSERT_ERROR(!actual.compare_no_user(expected[i]),"failed to send packet to e2c");
          end
        end
      end
    join

    test_e2c.end_test();
  endtask : test_ethcpu_lane

  task automatic wait_for_udp_packets(int lane, int udp_dest_port);
    automatic EthAxisPacket_t  actual_a;
    automatic EthXportPacket_t actual = new();
    automatic raw_pkt_t        rcv_raw,rcv_pay;
    automatic udp_hdr_t        rcv_udp;
    automatic eth_hdr_t        rcv_eth;
    automatic ipv4_hdr_t       rcv_ip;
    automatic int              try_count = 0;
    automatic int              PREAMBLE;

    PREAMBLE = NO_PREAMBLE;

    do begin
      ++try_count;
      // check if packet is for our port
      #100;
      eth[lane].peek(actual_a);
      actual.import_axis(actual_a);
      actual.tuser_to_tkeep();
      rcv_raw = actual.dump_bytes();
      if (PREAMBLE == ZERO_PREAMBLE) begin
        repeat(6) rcv_raw.delete(0); // strip preamble
      end
      decode_udp_pkt(rcv_raw,rcv_eth,rcv_ip,rcv_udp,rcv_pay);
      `ASSERT_ERROR(try_count != 100,"unclaimed packet on c2e");
      end while (rcv_udp.dest_port != udp_dest_port);

  endtask : wait_for_udp_packets

  task automatic test_cpueth(int num_samples[$]);
    fork
     if (!DISABLED[0]) test_cpueth_lane(0,num_samples);
     if (!DISABLED[1]) test_cpueth_lane(1,num_samples);
     if (!DISABLED[2]) test_cpueth_lane(2,num_samples);
     if (!DISABLED[3]) test_cpueth_lane(3,num_samples);
    join
  endtask : test_cpueth

  task automatic test_cpueth_lane(int lane, int num_samples[$]);
    TestExec test_c2e = new();
    automatic CpuXportPacket_t send[$];
    automatic EthXportPacket_t expected[$];
    automatic int sample_sum = 0;
    string lane_str;
    lane_str.itoa(lane);

    test_c2e.start_test({TEST_NAME," ",lane_str," CPU to Ethernet"}, 60us*5);
    // This path is
    //   c2e -> (eth_adapter) s_cpu ->
    ////  (ARM_DEFRAMER)(IF ARM)
    //   (eth_adapater) c2e ->
    ////  (ETH_MUX)(SIZE=2)
    //   (eth_adapater) m_mac -> eth_tx

    foreach (num_samples[i]) begin
      automatic eth_hdr_t    eth_hdr;
      automatic ipv4_hdr_t   ipv4_hdr;
      automatic udp_hdr_t    udp_hdr;
      automatic raw_pkt_t    pay,udp_raw;
      automatic int          PREAMBLE;
      PREAMBLE = NO_PREAMBLE;

      expected[i] = new;
      send[i] = new;

      get_ramp_raw_pkt(.num_samps(num_samples[i]),.ramp_start((sample_sum)%256),
                       .ramp_inc(1),.pkt(pay),.SWIDTH(8));
      sample_sum += num_samples[i];
      udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,pay,
                              .preamble(NO_PREAMBLE));
      send[i].push_bytes(udp_raw);
      send[i].tkeep_to_tuser();

      // rebuild the expected packet for comparison with a zero preamble
      udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,pay,
                              .preamble(PREAMBLE));
      //eth ifc expands pre CRC packets to 64 bytes on the C2E path
      if (SV_ETH_IFC) begin
        while (udp_raw.size < 64) begin
          udp_raw.push_back(0);
        end
      end;

      expected[i].push_bytes(udp_raw);
      expected[i].tkeep_to_tuser();
    end

    fork
      begin // tx_thread
        foreach(send[i])begin
          cpu[lane].put(send[i]);
        end
      end
      begin //rx_thread
        foreach(expected[i]) begin
          automatic EthAxisPacket_t  actual_a;
          automatic EthXportPacket_t actual = new();
          automatic raw_pkt_t        rcv_raw,rcv_pay;
          automatic udp_hdr_t        rcv_udp;
          automatic eth_hdr_t        rcv_eth;
          automatic ipv4_hdr_t       rcv_ip;
          automatic int              try_count = 0;

          wait_for_udp_packets(lane,DEF_DEST_UDP_PORT);
          eth[lane].get(actual_a);
          actual.import_axis(actual_a);
          if (!SV_ETH_IFC) begin
            actual.tuser_to_tkeep();
          end
         `ASSERT_ERROR(!actual.compare_w_pad(expected[i],!SV_ETH_IFC),"failed to send packet to c2e");
        end
      end
    join
    test_c2e.end_test();

  endtask : test_cpueth_lane

  //---------------------------------------------------------------------------
  // Ethernet to CHDR test
  //---------------------------------------------------------------------------

  function automatic word_queue_t bytes_to_words(raw_pkt_t pay);
    automatic ChdrXportPacket_t axis_pkt = new();

    axis_pkt.push_bytes(pay);
    return axis_pkt.data;

  endfunction : bytes_to_words;

  function automatic raw_pkt_t flatten_chdr(ChdrPacket_t chdr_pkt);
    automatic ChdrAxisPacket_t axis_chdr;
    automatic ChdrXportPacket_t xport_chdr = new();
    axis_chdr = chdr_pkt.chdr_to_axis();
    foreach (axis_chdr.data[i]) begin
      axis_chdr.keep[i] = '1;
      axis_chdr.user[i] = '0;
    end
    xport_chdr.import_axis(axis_chdr);
    return xport_chdr.dump_bytes();
  endfunction : flatten_chdr

  function automatic ChdrPacket_t unflatten_chdr(raw_pkt_t chdr_raw);
    automatic ChdrXportPacket_t xport_chdr = new();
    automatic ChdrPacket_t chdr_pkt = new();
    xport_chdr.push_bytes(chdr_raw);
    foreach (xport_chdr.data[i]) begin
      xport_chdr.keep[i] = '1;
      xport_chdr.user[i] = '0;
    end
    chdr_pkt.axis_to_chdr(xport_chdr);
    return chdr_pkt;
  endfunction : unflatten_chdr

  task automatic test_ethchdr(int num_samples[$], int ERROR_PROB=2, int EXPECT_DROPS=0);
    fork
      if (!DISABLED[0]) test_ethchdr_lane(0,num_samples,ERROR_PROB,EXPECT_DROPS);
      if (!DISABLED[1]) test_ethchdr_lane(1,num_samples,ERROR_PROB,EXPECT_DROPS);
      if (!DISABLED[2]) test_ethchdr_lane(2,num_samples,ERROR_PROB,EXPECT_DROPS);
      if (!DISABLED[3]) test_ethchdr_lane(3,num_samples,ERROR_PROB,EXPECT_DROPS);
    join
  endtask : test_ethchdr;

  task automatic test_ethchdr_lane(int lane, int num_samples[$], int ERROR_PROB=2, int EXPECT_DROPS=0);
    TestExec test_e2v = new();
    automatic EthXportPacket_t send[$];
    automatic ChdrXportPacket_t expected[$];
    automatic int sample_sum = 0;
    string lane_str;
    lane_str.itoa(lane);

    test_e2v.start_test({TEST_NAME," ",lane_str," Ethernet to CHDR"}, 60us);
    // This path is
    //   eth_rx -> s_mac(eth_adapter) -> s_mac(eth_dispatch) ->
    ////   in_reg(AXI_FIFO)(SIZE=1)
    //   (eth_dispatch) in -> STATMACHINE (Dispatch) + chdr ->
    ////   chdr_user_fifo(AXI_FIFO)(SIZE=8) (capture eth header)
    ////   chdr_out_gate(AXI_GATE)(SIZE=11)
    //   (eth_dispatch) o_chdr ->
    ////   chdr_trim(CHDR_TRIM_PAYLOAD)
    //   (eth_dispatch) m_chdr -> (eth_adapater) e2x_chdr -> (xport_adapter_gen) s_axis_xport
    ////   xport_in_swap (AXIS_DATA_SWAP)
    //   (xport_adapter_gen) i_xport ->
    ////   mgmt_ep(CHDR_MGMT_PKT_HANDLER)
    //   (xport_adapter_gen) x2d ->
    ////   rtn_demux(AXI_SWITCH)  x2x(loopback) or m_axis_rfnoc
    //   (xport_adapter_gen) m_axis_rfnoc -> (eth_adapter) e2x_fifo
    ////   chdr_fifo(AXI_FIFO)(SIZE=MTU)
    //   (eth_adapater) m_chdr -> e2v

    foreach (num_samples[i]) begin
      automatic eth_hdr_t    eth_hdr;
      automatic ipv4_hdr_t   ipv4_hdr;
      automatic udp_hdr_t    udp_hdr;
      automatic raw_pkt_t    pay,udp_raw,chdr_raw;

      automatic ChdrPacket_t chdr_pkt = new();
      automatic chdr_header_t chdr_hdr;
      automatic chdr_word_t chdr_ts;
      automatic chdr_word_t chdr_mdata[$];
      automatic chdr_word_t chdr_data[$];

      automatic int          PREAMBLE;
      PREAMBLE = NO_PREAMBLE;

      expected[i] = new;
      send[i] = new;

      // build a payload
      get_ramp_raw_pkt(.num_samps(num_samples[i]),.ramp_start((sample_sum)%256),
                       .ramp_inc(1),.pkt(pay),.SWIDTH(8));
      sample_sum += num_samples[i];
      // Fill data in the chdr packet
      chdr_hdr = '{
        vc        : 0,
        dst_epid  : 0,
        seq_num   : 0,
        pkt_type  : CHDR_DATA_NO_TS,
        num_mdata : 0,
        default   : 0
      };
      chdr_ts = 0;         // no timestamp
      chdr_mdata.delete(); // not adding meta data
      chdr_data = bytes_to_words(pay);

      chdr_pkt.write_raw(chdr_hdr, chdr_data, chdr_mdata, chdr_ts);
      chdr_raw =  flatten_chdr(chdr_pkt);

      //build a udp packet
      udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,chdr_raw,
                              .preamble(PREAMBLE));
      send[i].push_bytes(udp_raw);
      send[i].tkeep_to_tuser(.ERROR_PROB(ERROR_PROB));

      // expect just the chdr packet (UDP stripped)
      expected[i].push_bytes(chdr_raw);
      expected[i].tkeep_to_tuser();

    end

    // iterate in descending order so deleting doesn't shift down
    // the packets in future loop iterations.
    for (int i=  num_samples.size()-1 ; i >= 0; i--) begin
      // original only checks for errors in last word.
      if (!SV_ETH_IFC && send[i].has_error()) send[i].set_error();
      // If a packet has an error it shouldn't make it through
      if (send[i].has_error()) expected.delete(i);//MAC ERROR
    end

    fork
      begin // tx_thread
        foreach(send[i])begin
          #1 eth[lane].put(send[i]); // delay causes CPU/CHDR traffic on input to be interleaved.
        end
      end
      begin //rx_thread
        if (EXPECT_DROPS > 0) begin
          automatic int pkt_num = 0;
          automatic int drop_count = 0;
          while (expected.size() > 0) begin
            automatic ChdrAxisPacket_t  actual_a;
            automatic ChdrXportPacket_t actual = new();
            v[lane].get(actual_a);
            actual.import_axis(actual_a);
            actual.tuser_to_tkeep();
            while (expected.size > 0 && actual.compare_no_user(expected[0],.PRINT_LVL(0))) begin
              void'(expected.pop_front());
              ++drop_count;
              ++pkt_num;
               $display("Dropped packet %d",pkt_num);
              `ASSERT_ERROR(drop_count < EXPECT_DROPS,"Exceeded anticipated number of dropped packets e2v");
            end
            if (expected.size() > 0) begin
              ++pkt_num;
              $display("Rcvd packet   %d",pkt_num);
              void'(expected.pop_front());
            end
          end
          if (SV_ETH_IFC) begin
            $display("Verify drop count is %d",drop_count);
            axi.rd(REG_CHDR_DROPPED,drop_count);
          end
        end else begin
          foreach(expected[i]) begin
            automatic ChdrAxisPacket_t  actual_a;
            automatic ChdrXportPacket_t actual = new();
            v[lane].get(actual_a);
            actual.import_axis(actual_a);
            actual.tuser_to_tkeep();
           `ASSERT_ERROR(!actual.compare_no_user(expected[i]),"failed to send packet e2v");
          end
        end
      end
    join
    test_e2v.end_test();

  endtask : test_ethchdr_lane;


  task automatic test_chdreth(int num_samples[$]);
    fork
     if (!DISABLED[0]) test_chdreth_lane(0,num_samples);
     if (!DISABLED[1]) test_chdreth_lane(1,num_samples);
     if (!DISABLED[2]) test_chdreth_lane(2,num_samples);
     if (!DISABLED[3]) test_chdreth_lane(3,num_samples);
    join
  endtask : test_chdreth

  task automatic test_chdreth_lane(int lane, int num_samples[$]);
    TestExec test_v2e = new();
    automatic ChdrXportPacket_t send[$];
    automatic EthXportPacket_t expected[$];
    automatic int sample_sum = 0;
    string lane_str;
    lane_str.itoa(lane);

    test_v2e.start_test({TEST_NAME," ",lane_str," CHDR to Ethernet"}, 60us);
    // This path is
    //   v2e -> s_chdr(eth_adapter) -> s_axis_rfnoc (xport_adapter_gen) ->
    ////   axi_demux_mgmt_filter (AXI_DEMUX) (IF ALLOW_DISC) (discards discovery packets)
    //   (xport_adapter_gen) f2m ->
    ////   rtn_mux(AXI_MUX) between x2x and f2m
    //   (xport_adapter_gen) m2x ->
    ////   data_fifo/lookup_fifo (AXI_FIFO_SHORT)
    ////   LOOKUP LOGIC (lookup_fifo,data_fifo,results)
    //   (xport_adapter_gen) o_xport ->
    ////   xport_out_swap (AXIS_DATA_SWAP)
    //   (xport_adapter_gen) m_axis_xport -> (eth_adapater) x2e_chdr ->
    ////   ENET_HDR_LOGIC (frame_state)
    //   (eth_adapater) frame -> (eth_adapater) x2e_framed
    ////  (ETH_MUX)(SIZE=2)
    //   (eth_adapater) m_mac -> eth_tx

    foreach (num_samples[i]) begin
      automatic eth_hdr_t    eth_hdr;
      automatic ipv4_hdr_t   ipv4_hdr;
      automatic udp_hdr_t    udp_hdr;
      automatic raw_pkt_t    pay,udp_raw,chdr_raw;

      automatic ChdrPacket_t chdr_pkt = new();
      automatic chdr_header_t chdr_hdr;
      automatic chdr_word_t chdr_ts;
      automatic chdr_word_t chdr_mdata[$];
      automatic chdr_word_t chdr_data[$];

      automatic int          PREAMBLE;
      if (PROTOCOL0 == `MGT_100GbE) PREAMBLE = NO_PREAMBLE;
      else                          PREAMBLE = NO_PREAMBLE;

      // ModelSim should initialize the fields of ipv4_hdr to their default
      // values, but for some reason it doesn't in this case. We add an
      // initialization here to work around the bug for now.
      ipv4_hdr  = '{
        header_length : 4'd5,
        version       : 4'd4,
        dscp          : 6'b0000_00,
        ecn           : 2'b00,
        length        : 16'hXXXX,
        identification: 16'h462E,
        rsv_zero      : 1'b0,
        dont_frag     : 1'b1,
        more_frag     : 1'b0,
        frag_offset   : 16'd0,
        time_to_live  : 16'd64,
        protocol      : UDP,
        checksum      : 16'hXXXX,
        src_ip        : DEF_SRC_IP_ADDR,
        dest_ip       : DEF_DEST_IP_ADDR
      };

      expected[i] = new;
      send[i] = new;

      // build a payload
      get_ramp_raw_pkt(.num_samps(num_samples[i]),.ramp_start((sample_sum)%256),
                       .ramp_inc(1),.pkt(pay),.SWIDTH(8));
      sample_sum += num_samples[i];

      // Fill data in the chdr packet
      chdr_hdr = '{
        vc        : 0,
        dst_epid  : 0,
        seq_num   : 0,
        pkt_type  : CHDR_DATA_NO_TS,
        num_mdata : 0,
        default   : 0
      };
      chdr_ts = 0;         // no timestamp
      chdr_mdata.delete(); // not adding meta data
      chdr_data = bytes_to_words(pay);

      chdr_pkt.write_raw(chdr_hdr, chdr_data, chdr_mdata, chdr_ts);
      chdr_raw =  flatten_chdr(chdr_pkt);

      // send the raw chedar packet
      send[i].push_bytes(chdr_raw);
      send[i].tkeep_to_tuser();

      //build a udp packet
      // modify as the EthInterface does
      udp_hdr.src_port = DEF_DEST_UDP_PORT;
      udp_hdr.dest_port = 0; // Extract from router lookup results (Default)
      udp_hdr.checksum = 0;  // Checksum not calculated at this point
      ipv4_hdr.src_ip = DEF_DEST_IP_ADDR;
      ipv4_hdr.dest_ip = 0; // Extract from router lookup results (Default)
      ipv4_hdr.dscp      = 0; // hardcoded
      ipv4_hdr.dont_frag = 1; // hardcoded
      ipv4_hdr.identification = 0; // hardcoded
      ipv4_hdr.time_to_live = 8'h10; //hardcoded
      eth_hdr.src_mac = DEF_DEST_MAC_ADDR;
      eth_hdr.dest_mac = 0; // Extract from router lookup results (Default)

      udp_raw = build_udp_pkt(eth_hdr,ipv4_hdr,udp_hdr,chdr_raw,
                              .preamble(PREAMBLE));

      // XGE mac autoexpands all packet to size 60 (pre CRC) to meet 64 byte min packet
      if (AUTOEXPAND_TO_64) begin
        while (udp_raw.size < 60) begin
          udp_raw.push_back(0);
        end
      end;

      // expect udp wrapped chdr
      expected[i].push_bytes(udp_raw);
      if (IGNORE_EXTRA_DATA) begin
        expected[i].clear_user(); // expect all full words!
        expected[i].tuser_to_tkeep();
      end else begin
        expected[i].tkeep_to_tuser();
      end
    end

    fork
      begin // tx_thread
        foreach(send[i])begin
          v[lane].put(send[i]);
        end
      end
      begin //rx_thread
        foreach(expected[i]) begin
          automatic EthAxisPacket_t  actual_a;
          automatic EthXportPacket_t actual = new();
          automatic eth_hdr_t    eth_hdr;
          automatic ipv4_hdr_t   ipv4_hdr;
          automatic udp_hdr_t    udp_hdr;
          automatic raw_pkt_t    chdr_raw,actual_raw;
          automatic ChdrPacket_t chdr_pkt;
          automatic integer chdr_len;
          automatic logic [7:0] trash;
          localparam UDP_LEN = 8/*udp*/+20/*ipv4*/+14/*eth-no vlan*/;

          wait_for_udp_packets(.lane(lane),.udp_dest_port(0));
          eth[lane].get(actual_a);
          actual.import_axis(actual_a);
          // to get chdr_len
          actual_raw = actual.dump_bytes();

          repeat(PREAMBLE_BYTES) trash = actual_raw.pop_front();
          decode_udp_pkt(actual_raw,eth_hdr,ipv4_hdr,udp_hdr,chdr_raw);
          //chdr_pkt = unflatten_chdr(chdr_raw);
          if (IGNORE_EXTRA_DATA) begin
            for (int w=chdr_pkt.header.length+UDP_LEN;w <actual_raw.size();w++) begin
              actual_raw[w] = '0;
            end
            repeat(PREAMBLE_BYTES) actual_raw.push_front(0);
            actual.empty();
            actual.push_bytes(actual_raw);
          end
          `ASSERT_ERROR(!actual.compare_w_error(expected[i]),"failed to send packet v2e");
        end
      end
    join
    test_v2e.end_test();

  endtask : test_chdreth_lane


  //----------------------------------------------------
  //----------------------------------------------------
  //----------------------------------------------------
  // Main test loop
  //----------------------------------------------------
  //----------------------------------------------------
  //----------------------------------------------------
  initial begin : tb_main
    automatic int num_samples[$];
    automatic int cpu_num_samples[$];
    automatic int ERROR_PROB;
    localparam QUICK = 1;
    localparam CHECK_PAUSE = 0;

    // With QUANTA100/REFRESH200 I expected 50% slow down over
    // 1024 ns 200*5.12 ns (a Quanta is 512 bit times, bit time=10 ps)
    localparam PAUSE_QUANTA  = 100;
    localparam PAUSE_REFRESH = 200;

    //allocate BFM's
    eth = new[4];
    v   = new[4];
    cpu = new[4];

    //associate with virtual interfaces
    eth[0] = new(.master(eth_rx[0]), .slave(eth_tx[0]));
    eth[1] = new(.master(eth_rx[1]), .slave(eth_tx[1]));
    eth[2] = new(.master(eth_rx[2]), .slave(eth_tx[2]));
    eth[3] = new(.master(eth_rx[3]), .slave(eth_tx[3]));
    v[0]   = new(.master(v2e[0]), .slave(e2v[0]));
    v[1]   = new(.master(v2e[1]), .slave(e2v[1]));
    v[2]   = new(.master(v2e[2]), .slave(e2v[2]));
    v[3]   = new(.master(v2e[3]), .slave(e2v[3]));
    cpu[0] = new(.master(c2e[0]), .slave(e2c[0]));
    cpu[1] = new(.master(c2e[1]), .slave(e2c[1]));
    cpu[2] = new(.master(c2e[2]), .slave(e2c[2]));
    cpu[3] = new(.master(c2e[3]), .slave(e2c[3]));

    test.start_test({TEST_NAME,"::Wait for Reset"}, 10us);

    clk40_gen.reset();
    clk100_gen.reset();
    clk200_gen.reset();
    if (!USE_MAC) userclk_gen.reset();
    refclk_gen.reset();
    // start tready high - MAC doesn't have a tready so we need model
    // to always keep it's tready high
    foreach(eth[lane])begin
      if (!DISABLED[lane]) begin
        eth[lane].slave_tready_init = 1;
        eth[lane].run();
        cpu[lane].run();
        v[lane].run();
      end
    end
    axi.run();
    test_reset();

    test.end_test();

    foreach(eth[lane])begin
      if (!DISABLED[lane]) begin
        test_registers(lane);
      end
    end

    if (USE_MAC) begin
      automatic logic [31:0] data;
      automatic resp_t resp;

      // don't overflow/underflow the mac model
      foreach(eth[lane])begin
        if (!DISABLED[lane]) begin
          eth[lane].set_master_stall_prob(0);
          eth[lane].set_slave_stall_prob(0);
        end
      end
      // bit errors need to be generated in a different
      // way on the serial link.  (Not Yet Implemented)
      ERROR_PROB = 0;

      // 10GBE INIT
      foreach(eth[lane])begin
        if (IS10GBE[lane]) begin
          automatic int    LANE_BASE = 32'h10000 * lane;

          test.start_test({TEST_NAME,"::Wait for 10gbe MAC link_up"}, 150us);
          axi.wr(LANE_BASE+REG_MAC_CTRL_STATUS,1); // turn on tx_enable
          do begin
            axi.rd_block(LANE_BASE+REG_PORT_INFO,data,resp);
            assert (resp==OKAY);
          end while (data[16] !== 1);  //link_up
          test.end_test();
          test.start_test({TEST_NAME,"::Wait for 10gbe MODEL link_up"}, 150us);
          // check that model link is up
          do begin
            @(posedge clk40);
          end while (model_link_up[lane] !== 1);
          test.end_test();
        end
      end

      // 100GBE_INIT
      if (PROTOCOL0 == `MGT_100GbE) begin
        test.start_test({TEST_NAME,"::Wait for 100gbe phy_reset"}, 20us);
        // check that the DUT phy is out of reset.
        do begin
          axi.rd_block(REG_PHY_CTRL_STATUS,data,resp);
          assert (resp==OKAY);
        end while (data[1:0] !== 0);  //usr_tx_reset and usr_rx_reset

        test.end_test();
        test.start_test({TEST_NAME,"::Wait for 100gbe MODEL link_up"}, 150us);
        // check that model link is up
        do begin
          @(posedge clk40);
        end while (model_link_up[0] !== 1);
        test.end_test();
        test.start_test({TEST_NAME,"::Wait for 100gbe MAC link_up"}, 150us);
        // Added Autoconnect which is on by default.  uncomment to run manually
        //Pkg100gbMac::init_mac(32'h4000,axi);
        do begin
          axi.rd_block(REG_PORT_INFO,data,resp);
          assert (resp==OKAY);
        end while (data[16] !== 1);  //link_up

        test.end_test();
        test.start_test({TEST_NAME,"::Wait for auto config to complete"}, 10us);

        // check that the MAC auto config completed.
        do begin
          axi.rd_block(REG_MAC_CTRL_STATUS,data,resp);
          assert (resp==OKAY);
        end while (data[4] !== 1);  //auto config complete

        data[0]     = 1; // Autoconfig enable
        data[24:16] = 9'h100; // pause mask (use global pause)
        axi.wr(REG_MAC_CTRL_STATUS,data); // turn on tx_enable

        data[15:0]  = PAUSE_QUANTA;
        data[31:16] = PAUSE_QUANTA;
        axi.wr(REG_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG1+0*4,data);
        axi.wr(REG_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG1+1*4,data);
        axi.wr(REG_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG1+2*4,data);
        axi.wr(REG_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG1+3*4,data);
        data[15:0]  = PAUSE_QUANTA;
        data[31:16] = 0;
        axi.wr(REG_CONFIGURATION_TX_FLOW_CONTROL_QUANTA_REG1+4*4,data);

        data[15:0]  = PAUSE_REFRESH;
        data[31:16] = PAUSE_REFRESH;
        axi.wr(REG_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG1+0*4,data);
        axi.wr(REG_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG1+1*4,data);
        axi.wr(REG_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG1+2*4,data);
        axi.wr(REG_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG1+3*4,data);
        data[15:0]  = PAUSE_REFRESH;
        data[31:16] = 0;
        axi.wr(REG_CONFIGURATION_TX_FLOW_CONTROL_REFRESH_REG1+4*4,data);
        data[15:0]  = 64; // 64=4KBytes pause set
        data[31:16] = 32; // 32=2KBytes pause clear
        axi.wr(REG_PAUSE,data);
        axi.block();

        test.end_test();

      end
    end else begin
      ERROR_PROB = 2;
    end

    //Checks that pausing is working.
    if (CHECK_PAUSE) begin

      // This test will take an excessive time when USE_MAC is true.

      foreach(eth[lane])begin
        if (!DISABLED[lane]) begin
          eth[lane].set_master_stall_prob(0);
          eth[lane].set_slave_stall_prob(0);
          cpu[lane].set_master_stall_prob(0);
          cpu[lane].set_slave_stall_prob(0);
          v[lane].set_master_stall_prob(0);
          v[lane].set_slave_stall_prob(0);
        end
      end
      num_samples = {7936,7936,7936,7936,7936,320,
                     7936,7936,320,320,320,320,
                     // 280 512 byte packets (to help back things up)
                     512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,
                     512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,
                     512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,
                     512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,
                     512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,
                     512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,
                     512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,
                     512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,
                     512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,
                     512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,512,
                     7936,7936
                     };


      test_ethchdr(num_samples,.EXPECT_DROPS(0),.ERROR_PROB(0));
      // NOTE : The test ethcpu doesn't finish because the final packet doesn't
      // make it through, and the algorithm doesn't time out.
      num_samples = {7936,7936,7936,7936,7936,320,
                     7936,7936,320,320,320,320};
      test_ethcpu(num_samples,.EXPECT_DROPS(9),.ERROR_PROB(0));

    end

    foreach(eth[lane])begin
      if (!DISABLED[lane]) begin
        if (USE_MAC) begin
          eth[lane].set_master_stall_prob(0);
          eth[lane].set_slave_stall_prob(0);
          cpu[lane].set_master_stall_prob(0);
          cpu[lane].set_slave_stall_prob(0);
          v[lane].set_master_stall_prob(0);
          v[lane].set_slave_stall_prob(0);
        end else begin
          eth[lane].set_master_stall_prob(38);
          eth[lane].set_slave_stall_prob(38);
          cpu[lane].set_master_stall_prob(38);
          cpu[lane].set_slave_stall_prob(38);
          v[lane].set_master_stall_prob(38);
          v[lane].set_slave_stall_prob(38);
        end
      end
    end

    num_samples = {1,2,3,4,5,6,7,8,
                   ENET_W/8-1,ENET_W/8,ENET_W/8+1,
                   2*ENET_W/8-1,2*ENET_W/8,2*ENET_W/8+1,
                   CPU_W/8-1,CPU_W/8,CPU_W/8+1,
                   2*CPU_W/8-1,2*CPU_W/8,2*CPU_W/8+1,
                   CHDR_W/8-1,CHDR_W/8,CHDR_W/8+1,
                   2*CHDR_W/8-1,2*CHDR_W/8,2*CHDR_W/8+1
                  };
    //Option to add some extra samples for CPU packets to try to get
    //above the min packet size of 64. (just the headers makes up 42 bytes)
    //Packets less than 64 bytes should be padded to 64
    foreach (num_samples[i]) cpu_num_samples[i] = num_samples[i]+20;
    test.start_test({TEST_NAME,"::PacketW Combos NO Errors"}, 10us*5);
    fork // run in parallel
      // ethrx
      test_ethcpu(cpu_num_samples,.ERROR_PROB(0));
      test_ethchdr(num_samples,.ERROR_PROB(0));
      // ethtx
      test_chdreth(num_samples);
      test_cpueth(num_samples);
    join
    test.end_test();

    if (!QUICK) begin

      test.start_test({TEST_NAME,"::PacketW Combos Errors"}, 10us*5);
      fork // run in parallel
        // ethrx
        test_ethcpu(cpu_num_samples,.ERROR_PROB(ERROR_PROB));
        test_ethchdr(num_samples,.ERROR_PROB(ERROR_PROB));
        // ethtx
        test_chdreth(num_samples);
        test_cpueth(num_samples);
      join
      test.end_test();

      if (USE_MAC) begin
        // don't do huge packets with real MAC to save time
        num_samples = {16,32,64,128,256,512,1024,1500};
      end else begin
        num_samples = {16,32,64,128,256,512,1024,1500,1522,9000};
      end
      test.start_test({TEST_NAME,"::Pwr2 NoErrors"}, 60us*5);
      fork // run in parallel
        // ethrx
        test_ethcpu(cpu_num_samples,.ERROR_PROB(0));
        test_ethchdr(num_samples,.ERROR_PROB(0));
        // ethtx
        test_chdreth(num_samples);
        test_cpueth(num_samples);
     join
      test.end_test();
    end

    num_samples = {1,2,3,4,5,6,7,8,
                   ENET_W/8-1,ENET_W/8,ENET_W/8+1,
                   2*ENET_W/8-1,2*ENET_W/8,2*ENET_W/8+1,
                   CPU_W/8-1,CPU_W/8,CPU_W/8+1,
                   2*CPU_W/8-1,2*CPU_W/8,2*CPU_W/8+1,
                   CHDR_W/8-1,CHDR_W/8,CHDR_W/8+1,
                   2*CHDR_W/8-1,2*CHDR_W/8,2*CHDR_W/8+1
                  };
    test.start_test({TEST_NAME,"::Pktw NoStall+Error"}, 10us*5);
    fork // run in parallel
      // ethrx
      test_ethcpu(cpu_num_samples,.ERROR_PROB(ERROR_PROB));
      test_ethchdr(num_samples,.ERROR_PROB(ERROR_PROB));
      // ethtx
      test_chdreth(num_samples);
      test_cpueth(num_samples);
    join
    test.end_test();

    // repeat with back to back cpu/chdr packets
    test.start_test({TEST_NAME,"::Serial Pktw NoStall+Error"}, 10us*5);
    fork // run in parallel
      // ethrx
      begin
        test_ethcpu(cpu_num_samples,.ERROR_PROB(ERROR_PROB));
        test_ethchdr(num_samples,.ERROR_PROB(ERROR_PROB));
      end
      // ethtx
      begin
        test_chdreth(num_samples);
        test_cpueth(num_samples);
      end
    join
    test.end_test();

    // End the TB, but don't $finish, since we don't want to kill other
    // instances of this testbench that may be running.
    test.end_tb(0);

    // Kill the clocks to end this instance of the testbench
    clk40_gen.kill();
    clk100_gen.kill();
    clk200_gen.kill();
    if (!USE_MAC) userclk_gen.kill();
    refclk_gen.kill();
    done = 1;

  end // initial begin

endmodule
